namespace IconPatcher
{
    using System;
    using System.IO;
    using System.Linq;

    using IconPatcher.Annotations;

    [UsedImplicitly]
    public class InjectIcons : BetterConsoleCommand
    {
        private bool CleanUp;
        private const VisualStudioVersion Version = VisualStudioVersion.VS2012;

        private string ImageFolder;

        private string[] Languages;

        public InjectIcons()
        {
            IsCommand("inject", "Injects new icons into Visual Studio 2012.");

            HasOption("c|clean", "Deletes the images after injecting them.", a => CleanUp = true);
        }

        public override int Run(string[] remainingArguments)
        {
            if (!Program.Support2012)
            {
                ErrorMessage("You cannot run this command while in extract mode.");
                return 1;
            }

            if (!ForceVisualStudioToBeClosed("Inject Canceled"))
            {
                return 0;
            }

            string destination;
            var status = Helpers.GetInstallPath(Version, out destination);
            if (status != 0)
            {
                return status;
            }

            ImageFolder = Path.Combine(Environment.CurrentDirectory, "Images");
            if (!Directory.Exists(ImageFolder))
            {
                ErrorMessage("The image folder was not found.");
                return 1;
            }

            Languages = GetLanguagess(Version);

            Helpers.DllList.ForEach(dll => ProcessDll(destination, dll));

            if (CleanUp)
            {
                var imageFolder = Path.Combine(Environment.CurrentDirectory, "Images");
                Directory.Delete(imageFolder, true);
            }

            Helpers.ClearImageCache(Version);

            SuccessMessage("Inject Finished");

            return 0;
        }

        private void ProcessDll(string destination, DllInformation dll)
        {
            if (!dll.SupportsCulture)
            {
                InjectToDll("1033", destination, dll);
                return;
            }

            foreach (var language in Languages)
            {
                InjectToDll(language, destination, dll);
            }
        }

        private void InjectToDll(string language, string destination, DllInformation dll)
        {
            var dllPath = dll.ExpandedPath(language);

            var destinationDll = Path.Combine(destination, dllPath);

            if (!File.Exists(destinationDll))
            {
                LogMessage("File not found: {0}", destinationDll.Remove(destination));
                return;
            }

            var imageName = string.Format("{0}.{1}.bmp", new FileInfo(destinationDll).Name, dll.ImageIdString);
            var imageFile = Path.Combine(ImageFolder, imageName);
            if (!File.Exists(imageFile))
            {
                LogMessage("Image not found: {0}", imageFile.Remove(Environment.CurrentDirectory));
                return;
            }

            // we need to skip the header (first 14 bytes) in order to stick it into a dll
            var bytes = File.ReadAllBytes(imageFile).Skip(14).ToArray();

            var library = NativeMethods.BeginUpdateResource(destinationDll, false);
            if (library == IntPtr.Zero)
            {
                ErrorMessage("There was an issue patching file '{0}'", destinationDll.Remove(destination));
                ErrorMessage(Helpers.LastWin32ErrorMessage());
                return;
            }

            var languageId = Convert.ToUInt16(language);
            if (dll.UseId)
            {
                NativeMethods.UpdateResource(library, NativeMethods.RT_BITMAP, new IntPtr(dll.ImageId), languageId, bytes, (uint)bytes.Length);
            }
            else
            {
                NativeMethods.UpdateResource(library, NativeMethods.RT_BITMAP, dll.ImageName, languageId, bytes, (uint)bytes.Length);
            }

            var result = NativeMethods.EndUpdateResource(library, false);
            if (!result)
            {
                ErrorMessage("There was an issue patching file '{0}'", destinationDll.Remove(destination));
                ErrorMessage(Helpers.LastWin32ErrorMessage());
            }
        }
    }
}